package handler

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	"github.com/golang-jwt/jwt/v5"
	"github.com/lionsoul2014/ip2region/binding/golang/xdb"
	"gorm.io/gorm"
	"myai/core"
	"myai/core/types"
	"myai/service"
	"myai/store/model"
	"myai/store/model/req"
	"myai/store/model/rsp"
	"myai/store/vo"
	"myai/utils"
	"myai/utils/resp"
	"strings"
	"time"
)

// UserHandler 用户基本模块
type UserHandler struct {
	BaseHandler
	searcher    *xdb.Searcher
	redis       *redis.Client
	userService *service.UserService
}

func NewUserHandler(app *core.AppServer, db *gorm.DB, searcher *xdb.Searcher,
	client *redis.Client, userService *service.UserService) *UserHandler {
	return &UserHandler{
		BaseHandler: BaseHandler{DB: db, App: app},
		searcher:    searcher,
		redis:       client,
		userService: userService,
	}
}

// Register godoc
// @Summary      用户注册
// @Description  用户注册
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Param        req   body     req.UserRegisterReq  true  "用户注册信息"
// @Success      200  {object}  types.BizVo{data=map[string]any}
// @Router       /api/user/register [post]
func (h *UserHandler) Register(c *gin.Context) {
	var userRegisterReq req.UserRegisterReq
	// 把JSON请求参数转成结构体
	if err := c.ShouldBindJSON(&userRegisterReq); err != nil {
		resp.ERROR(c, types.InvalidArgs)
		return
	}
	// 判断密码长度
	userRegisterReq.Password = strings.TrimSpace(userRegisterReq.Password)
	if len(userRegisterReq.Password) < 8 {
		resp.ERROR(c, "密码长度不能小于8个字符")
		return
	}
	// 检查验证码
	var key string
	if userRegisterReq.RegWay == "email" {
		key = CodeStorePrefix + userRegisterReq.Email
		code, err := h.redis.Get(c, key).Result()
		if err != nil || code != userRegisterReq.Code {
			resp.ERROR(c, "验证码错误")
			return
		}
	} else if userRegisterReq.RegWay == "mobile" {
		key = CodeStorePrefix + userRegisterReq.Mobile
		code, err := h.redis.Get(c, key).Result()
		if err != nil || code != userRegisterReq.Code {
			resp.ERROR(c, "验证码错误")
			return
		}
	}
	// 验证邀请码
	inviteCode := model.InviteCode{}
	if userRegisterReq.InviteCode != "" {
		res := h.DB.Where("code = ?", userRegisterReq.InviteCode).First(&inviteCode)
		if res.Error != nil {
			resp.ERROR(c, "无效的邀请码")
			return
		}
	}

	salt := utils.RandString(8)
	user := model.User{
		Username:  userRegisterReq.Username,
		Password:  utils.GenPassword(userRegisterReq.Password, salt),
		Avatar:    "/images/avatar/user.png",
		Salt:      salt,
		Status:    true,
		ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
		Power:     h.App.SysConfig.InitPower,
	}
	// 判断用户名是否存在
	var item model.User
	// 创建一个新的数据库会话
	session := h.DB.Session(&gorm.Session{})
	if userRegisterReq.Mobile != "" {
		session = session.Where("mobile = ?", userRegisterReq.Mobile)
		user.Username = userRegisterReq.Mobile
		user.Mobile = userRegisterReq.Mobile
	} else if userRegisterReq.Email != "" {
		session = session.Where("email = ?", userRegisterReq.Email)
		user.Username = userRegisterReq.Email
		user.Email = userRegisterReq.Email
	} else if userRegisterReq.Username != "" {
		session = session.Where("username = ?", userRegisterReq.Username)
	}
	// 查询数据库，判断用户名是否已经被注册了
	session.First(&item)
	if item.Id > 0 {
		resp.ERROR(c, "该用户名已经被注册")
		return
	}

	// 被邀请人也获得赠送算力
	if userRegisterReq.InviteCode != "" {
		user.Power += h.App.SysConfig.InvitePower
	}
	user.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
	// 开启事务
	tx := h.DB.Begin()
	// 添加用户
	if err := tx.Create(&user).Error; err != nil {
		resp.ERROR(c, err.Error())
		return
	}
	// 记录邀请关系
	if userRegisterReq.InviteCode != "" {
		// 增加邀请数量
		h.DB.Model(&model.InviteCode{}).Where("code = ?", userRegisterReq.InviteCode).
			UpdateColumn("reg_num", gorm.Expr("reg_num + ?", 1))
		// 判断系统设置的 邀请新用户赠送算力值 是否大于0，如果大于0就增加邀请用户的算力值
		if h.App.SysConfig.InvitePower > 0 {
			// 增加算力
			err := h.userService.IncreasePower(int(inviteCode.UserId), h.App.SysConfig.InvitePower, model.PowerLog{
				Type:  types.PowerInvite,
				Model: "Invite",
				Remark: fmt.Sprintf("邀请用户注册奖励，金额：%d，邀请码：%s，新用户：%s", h.App.SysConfig.InvitePower,
					inviteCode.Code, user.Username),
			})
			if err != nil {
				tx.Rollback()
				resp.ERROR(c, err.Error())
				return
			}
		}
		// 添加邀请记录
		err := tx.Create(&model.InviteLog{
			InviterId:  inviteCode.UserId,
			UserId:     user.Id,
			Username:   user.Username,
			InviteCode: inviteCode.Code,
			Remark:     fmt.Sprintf("奖励 %d 算力", h.App.SysConfig.InvitePower),
		}).Error
		if err != nil {
			tx.Rollback()
			resp.ERROR(c, err.Error())
			return
		}
	}
	// 提交事务
	tx.Commit()
	_ = h.redis.Del(c, key) // 注册成功，删除短信验证码
	// 自动创建登录 token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"user_id": user.Id,
		"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
	})
	tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
	if err != nil {
		resp.ERROR(c, "生成Token失败："+err.Error())
		return
	}
	// 保存到 redis
	key = fmt.Sprintf("users/%d", user.Id)
	if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
		resp.ERROR(c, "保存Token失败："+err.Error())
		return
	}
	resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
}

// Login godoc
// @Summary      用户登陆
// @Description  用户登陆
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Param        req   body     req.UserLoginReq  true  "用户登录信息"
// @Success      200  {object}  types.BizVo{data=map[string]any}
// @Router       /api/user/login [post]
func (h *UserHandler) Login(c *gin.Context) {
	data := &req.UserLoginReq{}
	if err := c.ShouldBindJSON(&data); err != nil {
		resp.ERROR(c, types.InvalidArgs)
		return
	}
	var user model.User
	// 查询用户信息
	res := h.DB.Where("username = ?", data.Username).First(&user)
	if res.Error != nil {
		resp.ERROR(c, "用户名不存在")
		return
	}
	// 判断密码是否正确
	password := utils.GenPassword(data.Password, user.Salt)
	if password != user.Password {
		resp.ERROR(c, "用户名或密码错误")
		return
	}
	if user.Status == false {
		resp.ERROR(c, "该用户已经被禁止登录，请联系管理员")
		return
	}
	// 更新最后登录时间和IP
	user.LastLoginIp = c.ClientIP()
	user.LastLoginAt = time.Now().Unix()
	h.DB.Model(&user).Updates(user)
	// 记录登录日志
	h.DB.Create(&model.UserLoginLog{
		UserId:       user.Id,
		Username:     user.Username,
		LoginIp:      c.ClientIP(),
		LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
	})
	// 创建Token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"user_id": user.Id,
		"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
	})
	tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
	if err != nil {
		resp.ERROR(c, "生成Token失败："+err.Error())
		return
	}
	// 保存到 redis
	sessionKey := fmt.Sprintf("users/%d", user.Id)
	if _, err := h.redis.Set(c, sessionKey, tokenString, 0).Result(); err != nil {
		resp.ERROR(c, "保存Token失败："+err.Error())
		return
	}
	resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
}

// Logout godoc
// @Summary      注销
// @Description  注销
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Success      200  {object}  types.BizVo{}
// @Router       /api/user/logout [get]
func (h *UserHandler) Logout(c *gin.Context) {
	// 获取用户 Key
	key := h.GetUserKey(c)
	if _, err := h.redis.Del(c, key).Result(); err != nil {
		log.Errorf("删除用户session出错：%v", err)
	}
	resp.SUCCESS(c)
}

// Session godoc
// @Summary      获取/验证会话
// @Description  获取/验证会话
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Success      200  {object}  types.BizVo{data=vo.User}
// @Router       /api/user/session [get]
func (h *UserHandler) Session(c *gin.Context) {
	// 获取当前登录的用户信息
	user, err := h.GetLoginUser(c)
	if err != nil {
		resp.NotAuth(c, err.Error())
		return
	}
	var userVo vo.User
	err = utils.CopyObject(user, &userVo)
	if err != nil {
		resp.ERROR(c, err.Error())
		return
	}
	// 用户VIP到期
	if user.ExpiredTime > 0 && user.ExpiredTime < time.Now().Unix() {
		h.DB.Model(&user).UpdateColumn("vip", false)
	}
	userVo.Id = user.Id
	resp.SUCCESS(c, userVo)
}

// Profile godoc
// @Summary      获取用户的基本信息
// @Description  获取用户的基本信息
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Success      200  {object}  types.BizVo{data=rsp.UserProfileResp}
// @Router       /api/user/profile [get]
func (h *UserHandler) Profile(c *gin.Context) {
	user, err := h.GetLoginUser(c)
	if err != nil {
		resp.NotAuth(c)
		return
	}
	h.DB.First(&user, user.Id)
	var profile rsp.UserProfileResp
	err = utils.CopyObject(user, &profile)
	if err != nil {
		log.Errorf("对象拷贝失败：%v", err.Error())
		resp.ERROR(c, "获取用户信息失败")
		return
	}
	profile.Id = user.Id
	resp.SUCCESS(c, profile)
}

// ProfileUpdate godoc
// @Summary      更新用户基本信息
// @Description  更新用户基本信息
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Param        req   body     req.UserProfileReq  true  "用户更新信息"
// @Success      200  {object}  types.BizVo{}
// @Router       /api/user/profile_update [post]
// @Security ApiKeyAuth
func (h *UserHandler) ProfileUpdate(c *gin.Context) {
	var data req.UserProfileReq
	if err := c.ShouldBindJSON(&data); err != nil {
		resp.ERROR(c, types.InvalidArgs)
		return
	}
	user, err := h.GetLoginUser(c)
	if err != nil {
		resp.NotAuth(c)
		return
	}
	h.DB.First(&user, user.Id)
	user.Avatar = data.Avatar
	user.Nickname = data.Nickname
	res := h.DB.Updates(&user)
	if res.Error != nil {
		resp.ERROR(c, "更新用户信息失败")
		return
	}
	resp.SUCCESS(c)
}

// UpdatePass godoc
// @Summary      更新密码
// @Description  更新密码
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Param        req   body     req.UpdateUserPassReq  true  "用户更新用户密码信息"
// @Success      200  {object}  types.BizVo{}
// @Router       /api/user/update_pass [post]
// @Security ApiKeyAuth
func (h *UserHandler) UpdatePass(c *gin.Context) {
	var data req.UpdateUserPassReq
	if err := c.ShouldBindJSON(&data); err != nil {
		resp.ERROR(c, types.InvalidArgs)
		return
	}
	// 判断密码长度
	if len(data.Password) < 8 {
		resp.ERROR(c, "密码长度不能小于8个字符")
		return
	}
	user, err := h.GetLoginUser(c)
	if err != nil {
		resp.NotAuth(c)
		return
	}
	password := utils.GenPassword(data.OldPass, user.Salt)
	log.Debugf("用户密码盐：%s，用户数据库存储密码：%s，用户前端传递旧密码加密过：%s，用户前端传递旧密码未加密：%s",
		user.Salt, user.Password, password, data.OldPass)
	// 判断用户传递的旧密码是否和数据库中的密码相同
	if password != user.Password {
		resp.ERROR(c, "原密码错误")
		return
	}
	newPass := utils.GenPassword(data.Password, user.Salt)
	err = h.DB.Model(&user).UpdateColumn("password", newPass).Error
	if err != nil {
		resp.ERROR(c, "修改密码出现错误："+err.Error())
		return
	}
	resp.SUCCESS(c)
}

// ResetPass godoc
// @Summary      重置密码
// @Description  重置密码
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Param        req   body     req.UserRetrievalPasswordReq  true  "用户重置用户密码信息"
// @Success      200  {object}  types.BizVo{}
// @Router       /api/user/reset_pass [post]
func (h *UserHandler) ResetPass(c *gin.Context) {
	var data req.UserRetrievalPasswordReq
	if err := c.ShouldBindJSON(&data); err != nil {
		resp.ERROR(c, types.InvalidArgs)
		return
	}
	session := h.DB.Session(&gorm.Session{})
	var key string
	if data.Type == "email" {
		session = session.Where("email", data.Email)
		key = CodeStorePrefix + data.Email
	} else if data.Type == "mobile" {
		session = session.Where("mobile", data.Mobile)
		key = CodeStorePrefix + data.Mobile
	} else {
		resp.ERROR(c, "验证类别错误")
		return
	}
	var user model.User
	err := session.First(&user).Error
	if err != nil {
		resp.ERROR(c, "用户不存在")
		return
	}
	// 检查验证码
	code, err := h.redis.Get(c, key).Result()
	if err != nil || code != data.Code {
		resp.ERROR(c, "验证码错误")
		return
	}
	password := utils.GenPassword(data.Password, user.Salt)
	err = h.DB.Model(&user).UpdateColumn("password", password).Error
	if err != nil {
		resp.ERROR(c, "密码找回出现错误："+err.Error())
	} else {
		h.redis.Del(c, key)
		resp.SUCCESS(c)
	}
}

// BindMobile godoc
// @Summary      绑定手机号
// @Description  绑定手机号
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Param        req   body     req.UserBindMobileReq  true  "用户绑定手机号"
// @Success      200  {object}  types.BizVo{}
// @Router       /api/user/bind_mobile [post]
// @Security ApiKeyAuth
func (h *UserHandler) BindMobile(c *gin.Context) {
	var data req.UserBindMobileReq
	if err := c.ShouldBindJSON(&data); err != nil {
		resp.ERROR(c, types.InvalidArgs)
		return
	}
	// 检查验证码
	key := CodeStorePrefix + data.Mobile
	code, err := h.redis.Get(c, key).Result()
	if err != nil || code != data.Code {
		resp.ERROR(c, "验证码错误")
		return
	}
	// 检查手机号是否被别的其它账户绑定
	var item model.User
	res := h.DB.Where("mobile", data.Mobile).First(&item)
	if res.Error == nil {
		resp.ERROR(c, "该手机号已经绑定了其它账户，请更改手机号。")
		return
	}
	// 获取用户Id
	userId := h.GetLoginUserId(c)
	err = h.DB.Model(&item).Where("id", userId).UpdateColumn("mobile", data.Mobile).Error
	if err != nil {
		resp.ERROR(c, "绑定手机号失败："+err.Error())
		return
	}
	// 删除验证码
	_ = h.redis.Del(c, key)
	resp.SUCCESS(c)
}

// BindEmail godoc
// @Summary      绑定邮箱
// @Description  绑定邮箱
// @Tags         前台-用户接口
// @Accept       json
// @Produce      json
// @Param        req   body     req.UserBindEmailReq  true  "用户绑定邮箱"
// @Success      200  {object}  types.BizVo{}
// @Router       /api/user/bind_email [post]
// @Security ApiKeyAuth
func (h *UserHandler) BindEmail(c *gin.Context) {
	var data req.UserBindEmailReq
	if err := c.ShouldBindJSON(&data); err != nil {
		resp.ERROR(c, types.InvalidArgs)
		return
	}
	// 检查验证码
	key := CodeStorePrefix + data.Email
	code, err := h.redis.Get(c, key).Result()
	if err != nil || code != data.Code {
		resp.ERROR(c, "验证码错误")
		return
	}
	// 检查邮箱是否被别的其它账户绑定
	var item model.User
	res := h.DB.Where("email", data.Email).First(&item)
	if res.Error == nil {
		resp.ERROR(c, "该邮箱地址已经绑定了其它账户，请更改邮箱地址。")
		return
	}
	// 获取用户Id
	userId := h.GetLoginUserId(c)
	err = h.DB.Model(&item).Where("id", userId).UpdateColumn("email", data.Email).Error
	if err != nil {
		resp.ERROR(c, "绑定邮箱失败："+err.Error())
		return
	}
	// 删除验证码
	_ = h.redis.Del(c, key)
	resp.SUCCESS(c)
}
